package com.ikingtech.framework.sdk.oss.embedded.core.amazon;

import com.ikingtech.framework.sdk.oss.embedded.core.AbstractFileTemplate;
import com.ikingtech.framework.sdk.oss.embedded.core.OssInitMultiUploadResponse;
import com.ikingtech.framework.sdk.oss.embedded.core.OssResponse;
import com.ikingtech.framework.sdk.oss.embedded.exception.OssEmbeddedException;
import com.ikingtech.framework.sdk.utils.Tools;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.*;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author tie yan
 */
@RequiredArgsConstructor
public class AmazonS3FileTemplate extends AbstractFileTemplate {

    private final S3Client client;

    /**
     * 创建桶
     *
     * @param bucketName 桶名称
     */
    @Override
    public void createBucket(String bucketName) {
        this.client.createBucket(CreateBucketRequest.builder().bucket(bucketName).build());
    }

    @Override
    public void removeBucket(String bucketName) {
        throw new OssEmbeddedException("[N/A]unsupported operation[removeBucket]");
    }

    /**
     * 上传文件
     *
     * @param path        对象路径
     * @param objectName  对象名称
     * @param stream      文件流
     * @param contentType 媒体类型
     * @return 执行结果
     */
    @Override
    public String putObject(String path, String objectName, InputStream stream, String contentType) {
        return this.putObject(path, Tools.Str.EMPTY, objectName, stream, contentType);
    }

    @Override
    public String putObject(String path, String dir, String objectName, InputStream stream, String contentType) {
        try {
            String formattedObjectName = this.objectNameFormat(objectName);
            // 如果传入的dir不为空，则去除首位的/后拼接
            if (Tools.Str.isNotBlank(dir)) {
                dir = Tools.Str.removePrefix(dir, "/");
                dir = Tools.Str.removeSuffix(dir, "/");
                formattedObjectName = dir + "/" + formattedObjectName;
            }
            this.client.putObject(PutObjectRequest.builder()
                    .bucket(path)
                    .key(formattedObjectName)
                    .contentType(Tools.Str.isBlank(contentType) ? MediaType.APPLICATION_OCTET_STREAM_VALUE : contentType)
                    .build(), RequestBody.fromInputStream(stream, stream.available()));
            return formattedObjectName;
        } catch (IOException e) {
            throw new OssEmbeddedException("[N/A]put object error." + e.getMessage());
        }
    }

    /**
     * 创建分片上传文物
     *
     * @param path        对象路径
     * @param objectName  对象名称
     * @param contentType 文件传输类型
     * @return 执行结果
     */
    @Override
    public OssInitMultiUploadResponse initMultiUpload(String path, String objectName, String contentType) {
        String formattedObjectName = this.objectNameFormat(objectName);
        CreateMultipartUploadResponse multipartUpload = this.client.createMultipartUpload(CreateMultipartUploadRequest.builder()
                .bucket(path)
                .key(formattedObjectName)
                .contentType(Tools.Str.isBlank(contentType) ? MediaType.APPLICATION_OCTET_STREAM_VALUE : contentType)
                .build());
        return new OssInitMultiUploadResponse(multipartUpload.uploadId(), formattedObjectName);
    }

    /**
     * 分片上传文件
     *
     * @param path       对象路径
     * @param objectName 分片文件名
     * @param uploadId   分片上传编号
     * @param partNo     分片序号
     * @param stream     文件流
     */
    @Override
    public String putObjectSlice(String path, String objectName, String uploadId, Integer partNo, InputStream stream) {
        try {
            UploadPartResponse uploadPartResponse = this.client.uploadPart(UploadPartRequest.builder()
                    .bucket(path)
                    .key(objectName)
                    .uploadId(uploadId)
                    .partNumber(partNo)
                    .build(), RequestBody.fromInputStream(stream, stream.available()));
            return uploadPartResponse.eTag();
        } catch (IOException e) {
            throw new OssEmbeddedException("[N/A]put object slice error." + e.getMessage());
        }
    }

    /**
     * 完成分片上传文件
     *
     * @param path         对象路径
     * @param initResponse 分片上传任务创建结果
     * @param parts        分片信息，key为分片序号，value为分片上传返回的ETag
     * @return 执行结果
     */
    @Override
    public String completeMultiUpload(String path, OssInitMultiUploadResponse initResponse, Map<Integer, String> parts) {
        List<CompletedPart> completedParts = new ArrayList<>();
        parts.forEach((partNo, eTag) -> completedParts.add(CompletedPart.builder().partNumber(partNo).eTag(eTag).build()));
        this.client.completeMultipartUpload(CompleteMultipartUploadRequest.builder()
                .bucket(path)
                .key(initResponse.getObjectName())
                .uploadId(initResponse.getUploadId())
                .multipartUpload(CompletedMultipartUpload.builder()
                        .parts(completedParts)
                        .build())
                .build());
        return initResponse.getObjectName();
    }

    /**
     * 下载文件
     *
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @return 文件流
     */
    @Override
    public OssResponse getObject(String bucketName, String objectName) {
        ResponseInputStream<GetObjectResponse> responseStream = this.client.getObject(GetObjectRequest.builder().bucket(bucketName).key(objectName).build());
        return new OssResponse(responseStream.response().contentType(), responseStream);
    }

    /**
     * 删除桶
     *
     * @param bucketName 桶名称
     * @param objectName 对象名称
     */
    @Override
    public void removeObject(String bucketName, String objectName) {
        this.client.deleteObject(DeleteObjectRequest.builder().bucket(bucketName).key(objectName).build());
    }
}
